001 /*
002 * Copyright 2005 Stephen J. McConnell.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.library.console;
020
021 import java.io.File;
022 import java.io.IOException;
023 import java.net.URI;
024 import java.util.ArrayList;
025 import java.util.List;
026 import java.util.Map;
027 import java.util.Hashtable;
028
029 import net.dpml.util.Logger;
030
031 import net.dpml.lang.Part;
032
033 import net.dpml.library.Module;
034 import net.dpml.library.Resource;
035 import net.dpml.library.Builder;
036 import net.dpml.library.Type;
037 import net.dpml.library.info.ResourceDirective.Classifier;
038 import net.dpml.library.info.Scope;
039 import net.dpml.library.impl.DefaultLibrary;
040
041 import net.dpml.cli.Option;
042 import net.dpml.cli.Group;
043 import net.dpml.cli.CommandLine;
044 import net.dpml.cli.commandline.Parser;
045 import net.dpml.cli.util.HelpFormatter;
046 import net.dpml.cli.OptionException;
047 import net.dpml.cli.DisplaySetting;
048 import net.dpml.cli.builder.ArgumentBuilder;
049 import net.dpml.cli.builder.GroupBuilder;
050 import net.dpml.cli.builder.DefaultOptionBuilder;
051 import net.dpml.cli.builder.CommandBuilder;
052 import net.dpml.cli.option.PropertyOption;
053 import net.dpml.cli.validation.URIValidator;
054
055 /**
056 * Plugin that handles multi-project builds based on supplied commandline arguments.
057 *
058 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
059 * @version 1.0.0
060 */
061 public class BuilderPlugin
062 {
063 // ------------------------------------------------------------------------
064 // state
065 // ------------------------------------------------------------------------
066
067 private final Logger m_logger;
068 private final DefaultLibrary m_library;
069 private final Map m_map = new Hashtable();
070
071 private boolean m_verbose;
072 private boolean m_expand = false;
073
074 // ------------------------------------------------------------------------
075 // constructors
076 // ------------------------------------------------------------------------
077
078 /**
079 * Builder establishment. System property have already been assigned
080 * to the current jvm by depot.
081 *
082 * @param logger the assigned logging channel
083 * @param args supplimentary command line arguments
084 * @exception Exception if the build fails
085 */
086 public BuilderPlugin( Logger logger, String[] args )
087 throws Exception
088 {
089 m_logger = logger;
090 m_library = new DefaultLibrary( logger );
091
092 Thread.currentThread().setContextClassLoader( Builder.class.getClassLoader() );
093
094 Parser parser = new Parser();
095 parser.setGroup( COMMAND_GROUP );
096
097 try
098 {
099 CommandLine line = parser.parse( args );
100 m_verbose = line.hasOption( VERBOSE_OPTION );
101 if( line.hasOption( HELP_OPTION ) )
102 {
103 processHelp();
104 System.exit( 0 );
105 }
106 else
107 {
108 // setup the build version
109
110 if( line.hasOption( VERSION_OPTION ) )
111 {
112 String version = (String) line.getValue( VERSION_OPTION, "SNAPSHOT" );
113 System.setProperty( "build.signature", version );
114 if( m_verbose )
115 {
116 getLogger().info( "Setting version to: " + version );
117 }
118 }
119
120 if( line.hasOption( LIST_OPTION ) )
121 {
122 m_expand = line.hasOption( EXPAND_OPTION );
123 Resource[] resources = getTargetSelection( line );
124 if( resources.length == 0 )
125 {
126 getLogger().info( "Empty selection." );
127 System.exit( 0 );
128 }
129 else
130 {
131 list( resources );
132 }
133 }
134 else
135 {
136 Resource[] resources = getSelection( line );
137 if( resources.length == 0 )
138 {
139 getLogger().info( "Empty selection." );
140 System.exit( 0 );
141 }
142 boolean result = process( line, resources );
143 if( !result )
144 {
145 System.exit( 1 );
146 }
147 else
148 {
149 System.exit( 0 );
150 }
151 }
152 }
153 }
154 catch( OptionException e )
155 {
156 m_logger.error( e.getMessage() );
157 }
158 }
159
160 private Part createPart( URI uri ) throws Exception
161 {
162 try
163 {
164 Thread.currentThread().setContextClassLoader( Builder.class.getClassLoader() );
165 return Part.load( uri );
166 }
167 catch( Exception e )
168 {
169 final String error =
170 "Unexpected error occured while attempting to load builder.\nURI: "
171 + uri;
172 throw new BuilderError( error, e );
173 }
174 }
175
176 private Builder createBuilder( Part part ) throws Exception
177 {
178 Object[] params = new Object[]{m_logger, m_library, new Boolean( m_verbose )};
179 return (Builder) part.instantiate( params );
180 }
181
182 /**
183 * Resolve the project selection taking into account any overriding -s
184 * selection option, the -c switch, or in the absence of a selction, the
185 * implicit slection relative to the current working directory.
186 *
187 * @param line the commandline
188 * @return the resolved array of resources sorted relative to build sequence
189 */
190 private Resource[] getSelection( CommandLine line ) throws Exception
191 {
192 ArrayList list = new ArrayList();
193 Resource[] targets = getTargetSelection( line );
194 for( int i=0; i<targets.length; i++ )
195 {
196 Resource project = targets[i];
197 if( Classifier.LOCAL.equals( project.getClassifier() ) )
198 {
199 list.add( project );
200 }
201 }
202 return (Resource[]) list.toArray( new Resource[ list.size() ] );
203 }
204
205 /**
206 * Get the base selection and check if the consumer switch is present and
207 * if so build the consumers list from the selection list.
208 *
209 * @param line the commandline
210 * @return the array of projects in build order
211 */
212 private Resource[] getTargetSelection( CommandLine line ) throws Exception
213 {
214 Resource[] resources = getBaseSelection( line );
215 if( resources.length == 0 )
216 {
217 return resources;
218 }
219 boolean flag = line.hasOption( CONSUMERS_OPTION );
220 if( flag )
221 {
222 if( resources.length != 1 )
223 {
224 final String error =
225 "Consumer resolution against a multi-element selection is not supported.";
226 getLogger().error( error );
227 return new Resource[0];
228 }
229 else
230 {
231 Resource resource = resources[0];
232 return resource.getConsumers( true, true );
233 }
234 }
235 else
236 {
237 return resources;
238 }
239 }
240
241 /**
242 * Get the set of projects taking into consideration either the
243 * overriding selection option or the base directory if no selection specificed.
244 *
245 * @param line the commandline
246 * @return the array of projects in build order
247 */
248 private Resource[] getBaseSelection( CommandLine line ) throws Exception
249 {
250 String selection = (String) line.getValue( SELECT_OPTION, null );
251 if( null != selection )
252 {
253 getLogger().debug( "parsing selection: " + selection );
254 Resource[] resources = m_library.select( selection, false, true );
255 getLogger().debug( "selection: " + resources.length );
256 return resources;
257 }
258 else
259 {
260 String work = System.getProperty( "user.dir" );
261 getLogger().debug( "resolving selection in: " + work );
262 File file = new File( work ).getCanonicalFile();
263 Resource[] resources = m_library.select( file );
264 return resources;
265 }
266 }
267
268 /**
269 * Build the supplied set of projects. If a build filure occurs then
270 * abort the build sequence and exit.
271 *
272 * @param line the commandline
273 * @param resources the sorted sequence of projects to build
274 */
275 private boolean process( CommandLine line, Resource[] resources ) throws Exception
276 {
277 URI uri = (URI) line.getValue( BUILDER_URI_OPTION, ANT_BUILDER_URI );
278
279 Part part = createPart( uri );
280 Builder builder = createBuilder( part );
281
282 if( resources.length > 1 )
283 {
284 StringBuffer buffer =
285 new StringBuffer( "Initiating build sequence: (" + resources.length + ")\n" );
286 for( int i=0; i<resources.length; i++ )
287 {
288 Resource resource = resources[i];
289 buffer.append( "\n (" + ( i+1 ) + ")\t" + resource.getResourcePath() );
290 }
291 buffer.append( "\n" );
292 getLogger().info( buffer.toString() );
293 }
294
295 List list = line.getValues( TARGETS );
296 String[] targets = (String[]) list.toArray( new String[ list.size() ] );
297 for( int i=0; i<resources.length; i++ )
298 {
299 Resource resource = resources[i];
300 boolean status = builder.build( resource, targets );
301 if( !status )
302 {
303 return status;
304 }
305 System.gc();
306 }
307 return true;
308 }
309
310 private static final URI ANT_BUILDER_URI;
311
312 static
313 {
314 try
315 {
316 ANT_BUILDER_URI = new URI( "artifact:part:dpml/depot/dpml-tools-builder#1.0.0" );
317 }
318 catch( Exception e )
319 {
320 throw new RuntimeException( "will not happen", e );
321 }
322 }
323
324 /**
325 * Build the supplied set of projects. If a build filure occurs then
326 * abort the build sequence and exit.
327 *
328 * @param resources the sorted sequence of prouject to build
329 * @exception Exception if an error occurs
330 */
331 private void list( Resource[] resources ) throws Exception
332 {
333 if( resources.length == 1 )
334 {
335 Resource resource = resources[0];
336 if( resource instanceof Module )
337 {
338 listModule( (Module) resource );
339 }
340 else
341 {
342 listResource( resource );
343 }
344 }
345 else
346 {
347 listResources( resources );
348 }
349 }
350
351 private void listModule( Module module ) throws Exception
352 {
353 print( "Listing module [" + module.getResourcePath() + "]\n" );
354 listResource( " ", module, 0 );
355 print( "" );
356 }
357
358 private void listResource( Resource project ) throws Exception
359 {
360 print( "Listing project: " + project.getResourcePath() + "\n" );
361 listResource( " ", project, 0 );
362 print( "" );
363 }
364
365 private void listResources( Resource[] resources ) throws Exception
366 {
367 print( "Selection: [" + resources.length + "]\n" );
368 for( int i=0; i<resources.length; i++ )
369 {
370 Resource resource = resources[i];
371 String label = getLabel( i+1 );
372 print( label + resource );
373 }
374 print( "" );
375 }
376
377
378 private void listResource( String pad, Resource resource, int n ) throws Exception
379 {
380 if( n > 0 )
381 {
382 print( "\n[" + n + "] " + resource );
383 }
384 else
385 {
386 print( "\n" + resource );
387 }
388 print( "" );
389 print( pad + "version: " + resource.getVersion() );
390 print( pad + "basedir: " + resource.getBaseDir() );
391 String p = pad + " ";
392 Type[] types = resource.getTypes();
393 if( types.length > 0 )
394 {
395 print( pad + "types: (" + types.length + ")" );
396 for( int i=0; i<types.length; i++ )
397 {
398 print( p + types[i].getID() );
399 }
400 }
401
402 Resource[] resources = resource.getProviders( Scope.BUILD, m_expand, true );
403 if( resources.length > 0 )
404 {
405 print( pad + "build phase providers: (" + resources.length + ")" );
406 for( int i=0; i<resources.length; i++ )
407 {
408 Resource res = resources[i];
409 print( p + res );
410 }
411 }
412 resources = resource.getProviders( Scope.RUNTIME, m_expand, true );
413 if( resources.length > 0 )
414 {
415 print( pad + "runtime providers: (" + resources.length + ")" );
416 for( int i=0; i<resources.length; i++ )
417 {
418 Resource res = resources[i];
419 print( p + res );
420 }
421 }
422 resources = resource.getProviders( Scope.TEST, m_expand, true );
423 if( resources.length > 0 )
424 {
425 print( pad + "test providers: (" + resources.length + ")" );
426 for( int i=0; i<resources.length; i++ )
427 {
428 Resource res = resources[i];
429 print( p + res );
430 }
431 }
432 }
433
434 static void print( String message )
435 {
436 System.out.println( message );
437 }
438
439 private static String getLabel( int n )
440 {
441 StringBuffer buffer = new StringBuffer();
442 buffer.append( " [" + n );
443 buffer.append( "]" );
444 buffer.append( " " );
445 String tag = buffer.toString();
446 return tag.substring( 0, 7 ) + " ";
447 }
448
449 /**
450 * List general command help to the console.
451 * @exception IOException if an I/O error occurs
452 */
453 private void processHelp() throws IOException
454 {
455 HelpFormatter formatter = new HelpFormatter(
456 HelpFormatter.DEFAULT_GUTTER_LEFT,
457 HelpFormatter.DEFAULT_GUTTER_CENTER,
458 HelpFormatter.DEFAULT_GUTTER_RIGHT,
459 100 );
460
461 formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_GROUP_OUTER );
462 formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION );
463 formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED );
464
465 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_OPTIONAL );
466 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_GROUP_OUTER );
467 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION );
468 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_OPTIONAL );
469 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED );
470 formatter.getFullUsageSettings().remove( DisplaySetting.DISPLAY_PARENT_CHILDREN );
471
472 formatter.getLineUsageSettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION );
473 formatter.getLineUsageSettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED );
474 formatter.getLineUsageSettings().remove( DisplaySetting.DISPLAY_PARENT_CHILDREN );
475 formatter.getLineUsageSettings().remove( DisplaySetting.DISPLAY_GROUP_EXPANDED );
476
477 formatter.setGroup( COMMAND_GROUP );
478 formatter.setShellCommand( "build" );
479 formatter.print();
480 }
481
482 private Logger getLogger()
483 {
484 return m_logger;
485 }
486
487 private static final DefaultOptionBuilder OPTION_BUILDER = new DefaultOptionBuilder();
488 private static final ArgumentBuilder ARGUMENT_BUILDER = new ArgumentBuilder();
489 private static final GroupBuilder GROUP_BUILDER = new GroupBuilder();
490 private static final CommandBuilder COMMAND_BUILDER = new CommandBuilder();
491 private static final PropertyOption PROPERTY_OPTION = new PropertyOption();
492
493 private static final Option HELP_OPTION =
494 OPTION_BUILDER
495 .withShortName( "help" )
496 .withShortName( "h" )
497 .withDescription( "List command help." )
498 .withRequired( false )
499 .create();
500
501 private static final Option SELECT_OPTION =
502 OPTION_BUILDER
503 .withShortName( "select" )
504 .withShortName( "s" )
505 .withDescription( "Build selected project(s)." )
506 .withRequired( false )
507 .withArgument(
508 ARGUMENT_BUILDER
509 .withDescription( "Project." )
510 .withName( "pattern" )
511 .withMinimum( 1 )
512 .withMaximum( 1 )
513 .create() )
514 .create();
515
516 private static final Option VERBOSE_OPTION =
517 OPTION_BUILDER
518 .withShortName( "verbose" )
519 .withShortName( "v" )
520 .withDescription( "Enable verbose mode." )
521 .withRequired( false )
522 .create();
523
524 private static final Option LIST_OPTION =
525 OPTION_BUILDER
526 .withShortName( "list" )
527 .withShortName( "l" )
528 .withDescription( "List selected project(s)." )
529 .withRequired( false )
530 .withArgument(
531 ARGUMENT_BUILDER
532 .withDescription( "Project." )
533 .withName( "pattern" )
534 .withMinimum( 0 )
535 .withMaximum( 1 )
536 .create() )
537 .create();
538
539 private static final Option EXPAND_OPTION =
540 OPTION_BUILDER
541 .withShortName( "expand" )
542 .withShortName( "e" )
543 .withDescription( "Expand dependencies." )
544 .withRequired( false )
545 .create();
546
547 private static final Option CONSUMERS_OPTION =
548 OPTION_BUILDER
549 .withShortName( "consumers" )
550 .withShortName( "c" )
551 .withDescription( "Consumer switch." )
552 .withRequired( false )
553 .create();
554
555 private static final Option VERSION_OPTION =
556 OPTION_BUILDER
557 .withShortName( "version" )
558 .withDescription( "Build output artifact version." )
559 .withRequired( false )
560 .withArgument(
561 ARGUMENT_BUILDER
562 .withDescription( "Created artifact version." )
563 .withName( "version" )
564 .withMinimum( 1 )
565 .withMaximum( 1 )
566 .create() )
567 .create();
568
569 private static final Option BUILDER_URI_OPTION =
570 OPTION_BUILDER
571 .withShortName( "plugin" )
572 .withDescription( "Default builder plugin uri." )
573 .withRequired( false )
574 .withArgument(
575 ARGUMENT_BUILDER
576 .withDescription( "Artifact reference." )
577 .withName( "artifact" )
578 .withMinimum( 1 )
579 .withMaximum( 1 )
580 .withValidator( new URIValidator() )
581 .create() )
582 .create();
583
584 private static final Option TARGETS =
585 ARGUMENT_BUILDER
586 .withName( "target" )
587 .create();
588
589 private static final Group LIST_GROUP =
590 GROUP_BUILDER
591 .withMinimum( 0 )
592 .withOption( LIST_OPTION )
593 .withOption( EXPAND_OPTION )
594 .withOption( CONSUMERS_OPTION )
595 .create();
596
597 private static final Group BUILD_GROUP =
598 GROUP_BUILDER
599 .withMinimum( 0 )
600 .withOption( SELECT_OPTION )
601 .withOption( CONSUMERS_OPTION )
602 .withOption( VERBOSE_OPTION )
603 .withOption( BUILDER_URI_OPTION )
604 .withOption( VERSION_OPTION )
605 .withOption( PROPERTY_OPTION )
606 .create();
607
608 private static final Group COMMAND_GROUP =
609 GROUP_BUILDER
610 .withName( "options" )
611 .withOption( HELP_OPTION )
612 .withOption( LIST_GROUP )
613 .withOption( BUILD_GROUP )
614 .withOption( TARGETS )
615 .create();
616 }
617